package client

import (
	"context"
	"errors"
	"go-rpc-2/conn_pool"
	"go-rpc-2/rpc/client/one_way"
	"go-rpc-2/rpc/encoding_protocol"
	"go-rpc-2/rpc/proxy"
	"go-rpc-2/rpc/serialize_protocol"
	"net"
	"reflect"
	"strconv"
	"sync"
	"time"
)

type Client struct {
	*encoding_protocol.ConnMsg
	pool       *conn_pool.ConnPool
	serializer serialize_protocol.Serializer // 客户端一般只有一种，服务端处理多个客户端 有多种
	services   map[string]proxy.Service
}

func (c *Client) BindProxy(services ...proxy.Service) {
	for _, service := range services {
		typ := reflect.TypeOf(service)
		val := reflect.ValueOf(service)

		if typ.Kind() == reflect.Ptr {
			typ = typ.Elem()
			val = val.Elem()
		}
		methodNum := typ.NumField()

		for i := 0; i < methodNum; i++ {
			methodTyp := typ.Field(i)
			methodVal := val.Field(i)

			if methodVal.CanSet() {
				fn := reflect.MakeFunc(methodTyp.Type, func(args []reflect.Value) (results []reflect.Value) {
					ctx := args[0].Interface().(context.Context)
					meta := make(map[string]string, 4)
					ok := one_way.HasOneWay(ctx)
					if ok {
						meta["one-way"] = "true" // 使用字符串，避免使用布尔等其他类型导致传输编码出现问题
					}
					var deadline time.Time
					deadline, ok = ctx.Deadline() // ok=false表示没设置过期时间
					if ok {
						meta["deadline"] = strconv.FormatInt(deadline.UnixMilli(), 10)
					}

					req := &encoding_protocol.Request{
						Serializer:  c.serializer.Code(),
						ServiceName: service.Name(),
						MethodName:  methodTyp.Name,
						Meta:        meta,
					}
					data := args[1].Interface()
					bs, err := c.serializer.Encode(data)
					resData := reflect.New(methodTyp.Type.Out(0).Elem())

					if err != nil {
						return []reflect.Value{
							resData,
							reflect.ValueOf(err),
						}
					}
					req.Data = bs

					wg := sync.WaitGroup{}
					wg.Add(1)
					var res *encoding_protocol.Response
					go func() {
						res = c.Invoke(ctx, req)
						wg.Done()
					}()

					wg.Wait()
					if res.Err != "" {
						return []reflect.Value{
							resData,
							reflect.ValueOf(errors.New(res.Err)),
						}
					}

					err = c.serializer.Decode(res.Data, resData.Interface())
					if err != nil {
						return []reflect.Value{
							resData,
							reflect.ValueOf(err),
						}
					}

					return []reflect.Value{
						resData,
						reflect.Zero(reflect.TypeOf(new(error)).Elem()),
					}
				})

				methodVal.Set(fn)
			}

		}
	}
}

func (c *Client) Invoke(ctx context.Context, request *encoding_protocol.Request) *encoding_protocol.Response {
	res := &encoding_protocol.Response{}
	conn, err := c.pool.Get(ctx)
	if err != nil {
		res.Err = err.Error()
		return res
	}
	reqBs := request.Encode()
	err = c.Write(conn, reqBs)
	if err != nil {
		res.Err = err.Error()
		return res
	}
	var resBs []byte
	resBs, err = c.Read(conn)
	if err != nil {
		res.Err = err.Error()
		return res
	}

	err = res.Decode(resBs)
	if err != nil {
		res.Err = err.Error()
		return res
	}
	return res
}

type Opt func(c *Client)

func WithSerializer(serializer serialize_protocol.Serializer) Opt {
	return func(c *Client) {
		c.serializer = serializer
	}
}

func NewClient(network, addr string, opts ...Opt) (*Client, error) {
	pool, err := conn_pool.NewConnPool(&conn_pool.Config{
		InitNum:    2,
		MaxNum:     4,
		MaxIdleNum: 3,
		MaxReqNum:  20,
		IdleTime:   time.Second * 3,
		Factory: func() (net.Conn, error) {
			return net.Dial(network, addr)
		},
	})
	if err != nil {
		return nil, err
	}
	c := &Client{
		pool: pool,
	}

	for _, opt := range opts {
		opt(c)
	}
	if c.serializer == nil {
		c.serializer = &serialize_protocol.JsonSerializer{}
	}

	return c, nil
}

var _ proxy.Proxy = (*Client)(nil)
